﻿using System.Text.RegularExpressions;

namespace Away.NetMap.Nmap;

[ServiceInject(ServiceLifetime.Scoped, true)]
public class ServiceProbeFile
{
    /// <summary>
    /// 排除端口
    /// </summary>
    public List<int> ExcludePorts { get; set; }
    /// <summary>
    /// 探针集合
    /// </summary>
    public List<ServiceProbe> Probes { get; set; }

    private ServiceProbe? _probe;

    public ServiceProbeFile()
    {
        ExcludePorts = new List<int>();
        Probes = new List<ServiceProbe>();

        FileToEntity();
    }

    private void FileToEntity()
    {
        var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Nmap", "nmap-service-probes");
        using var sr = File.OpenText(filePath);

        while (!sr.EndOfStream)
        {
            var lineTxt = sr.ReadLine();
            if (string.IsNullOrWhiteSpace(lineTxt) || lineTxt.StartsWith('#'))
            {
                continue;
            }

            if (ParseExcludePorts(lineTxt))
            {
                continue;
            }

            if (lineTxt.StartsWith("Probe"))
            {
                if (_probe != null)
                {
                    Probes.Add(_probe);
                }
                _probe = new();
            }

            _probe!.Parse(lineTxt);
        }
    }

    public bool ParseExcludePorts(string lineTxt)
    {
        var pattern = "Exclude T:(?<start>.*)-(?<end>.*)";
        var reg = Regex.Match(lineTxt, pattern);
        if (!reg.Success)
        {
            return false;
        }
        var start = Convert.ToInt32(reg.Result("${start}"));
        var end = Convert.ToInt32(reg.Result("${end}"));
        ExcludePorts.AddRange(Enumerable.Range(start, end - start + 1));
        return true;
    }
}


/// <summary>
/// 服务探针
/// </summary>
public class ServiceProbe
{
    /// <summary>
    /// 网络协议
    /// </summary>
    public string Protocol { get; set; }
    /// <summary>
    /// 探针名称
    /// </summary>
    public string ProbeName { get; set; }
    /// <summary>
    /// 探针参数
    /// </summary>
    public string ProbeString { get; set; }
    /// <summary>
    /// 探针超时时间/毫秒
    /// </summary>
    public int TotalWaitms { get; set; }
    /// <summary>
    /// 该指令仅用于NULL探针
    /// </summary>
    public int TcpWrappedms { get; set; }
    /// <summary>
    /// 探针权重
    /// </summary>
    public int Rarity { get; set; }
    /// <summary>
    /// 扫描端口
    /// </summary>
    public List<int> Ports { get; set; }
    /// <summary>
    /// 扫描ssl端口
    /// </summary>
    public List<int> SslPorts { get; set; }
    /// <summary>
    /// 此指令用于指定哪个探针作为回退
    /// </summary>
    public string Fallback { get; set; }
    /// <summary>
    /// 匹配服务集合
    /// </summary>
    public List<ProbeMatchItem> Matches { get; private set; }

    public ServiceProbe()
    {
        ProbeName = string.Empty;
        Protocol = string.Empty;
        ProbeString = string.Empty;
        Fallback = string.Empty;
        Ports = new List<int>();
        SslPorts = new List<int>();
        Matches = new List<ProbeMatchItem>();
    }

    public bool Parse(string input)
    {
        if (ProbeParse(input))
        {
            return true;
        }
        if (RarityParse(input))
        {
            return true;
        }
        if (PortsParse(input))
        {
            return true;
        }
        if (SslPortsParse(input))
        {
            return true;
        }
        if (TotalWaitmsParse(input))
        {
            return true;
        }
        if (TcpWrappedmsParse(input))
        {
            return true;
        }
        if (MatchParse(input))
        {
            return true;
        }
        if (SoftMatchParse(input))
        {
            return true;
        }
        return false;
    }
    private bool ProbeParse(string input)
    {
        var pattern = "Probe (?<protocol>.*) (?<probename>.*) q[|](?<probestring>.*)[|].*";
        var reg = Regex.Match(input, pattern);
        if (!reg.Success)
        {
            return false;
        }

        Protocol = reg.Result("${protocol}");
        ProbeName = reg.Result("${probename}");
        ProbeString = Regex.Unescape(reg.Result("${probestring}"));

        return true;
    }
    private bool PortsParse(string input)
    {
        var pattern = @"^ports";
        if (!Regex.IsMatch(input, pattern))
        {
            return false;
        }
        var res = Regex.Replace(input, pattern, "");
        Ports = Regex.Split(res, ",").SelectMany(PortStrToNums).ToList();
        return true;
    }
    private bool SslPortsParse(string input)
    {
        var pattern = @"^sslports";
        if (!Regex.IsMatch(input, pattern))
        {
            return false;
        }
        var res = Regex.Replace(input, pattern, "");
        SslPorts = Regex.Split(res, ",").SelectMany(PortStrToNums).ToList();
        return true;
    }
    private List<int> PortStrToNums(string input)
    {
        if (input.Contains('-'))
        {
            var items = input.Split('-');
            var start = Convert.ToInt32(items[0]);
            var end = Convert.ToInt32(items[1]);
            return Enumerable.Range(start, end - start + 1).ToList();
        }
        else
        {
            return new List<int> { Convert.ToInt32(input) };
        }
    }
    private bool RarityParse(string input)
    {
        var pattern = "rarity (?<num>[0-9]+)";
        var reg = Regex.Match(input, pattern);
        if (!reg.Success)
        {
            return false;
        }
        Rarity = Convert.ToInt32(reg.Result("${num}"));
        return true;
    }
    private bool TotalWaitmsParse(string input)
    {
        var pattern = "totalwaitms (?<num>[0-9]+)";
        var reg = Regex.Match(input, pattern);
        if (!reg.Success)
        {
            return false;
        }
        TotalWaitms = Convert.ToInt32(reg.Result("${num}"));
        return true;
    }
    private bool TcpWrappedmsParse(string input)
    {
        var pattern = "^tcpwrappedms (?<num>[0-9]+)";
        var reg = Regex.Match(input, pattern);
        if (!reg.Success)
        {
            return false;
        }
        TcpWrappedms = Convert.ToInt32(reg.Result("${num}"));
        return true;
    }
    private bool MatchParse(string input)
    {
        ProbeMatchItem match = new();
        var isOk = match.Parse(input);
        if (isOk)
        {
            Matches.Add(match);
        }
        return isOk;
    }
    private bool SoftMatchParse(string input)
    {
        ProbeSoftMatchItem softmatch = new();
        var isOk = softmatch.Parse(input);
        if (isOk && Matches.Count > 0)
        {

            Matches.Last().SoftMatches.Add(softmatch);
        }
        return isOk;
    }

}

/// <summary>
/// 服务探针
/// </summary>
public class ProbeMatchItem : ProbeSoftMatchItem
{
    ///// <summary>
    ///// 服务名称
    ///// </summary>
    //public string Service { get; set; }
    ///// <summary>
    ///// 服务探针参数
    ///// </summary>
    //public string Pattern { get; set; }
    ///// <summary>
    ///// 服务版本信息
    ///// </summary>
    //public ProbeVersionInfo Info { get; set; }

    /// <summary>
    /// 子探针列表
    /// </summary>
    public List<ProbeSoftMatchItem> SoftMatches { get; private set; }

    public ProbeMatchItem()
    {
        Service = string.Empty;
        Pattern = string.Empty;
        Info = new ProbeVersionInfo();
        SoftMatches = new List<ProbeSoftMatchItem>();
    }

    public override bool Parse(string input)
    {
        var pattern = "^match (?<service>.*) m[|](?<pattern>.*)[|].*";
        var reg = Regex.Match(input, pattern);
        if (!reg.Success)
        {
            return false;
        }

        Service = reg.Result("${service}");
        Pattern = reg.Result("${pattern}");
        Info = new();
        Info.Parse(input);
        return true;
    }
}

/// <summary>
/// 服务子探针
/// </summary>
public class ProbeSoftMatchItem
{
    /// <summary>
    /// 服务名称
    /// </summary>
    public string Service { get; set; }
    /// <summary>
    /// 服务探针参数
    /// </summary>
    public string Pattern { get; set; }
    /// <summary>
    /// 服务版本信息
    /// </summary>
    public ProbeVersionInfo Info { get; set; }

    public ProbeSoftMatchItem()
    {
        Service = string.Empty;
        Pattern = string.Empty;
        Info = new ProbeVersionInfo();
    }

    public virtual bool Parse(string input)
    {
        var pattern = "^softmatch (?<service>.*) m[|](?<pattern>.*)[|].*";
        var reg = Regex.Match(input, pattern);
        if (!reg.Success)
        {
            return false;
        }

        Service = reg.Result("${service}");
        Pattern = reg.Result("${pattern}");

        Info = new();
        Info.Parse(input);
        return true;
    }
}

/// <summary>
/// 版本信息
/// </summary>
public class ProbeVersionInfo
{
    /// <summary>
    /// 供应商或者服务商
    /// </summary>
    public string Production { get; set; }
    /// <summary>
    /// 版本
    /// </summary>
    public string Version { get; set; }
    /// <summary>
    /// 详情
    /// </summary>
    public string Information { get; set; }
    /// <summary>
    /// 主机名
    /// </summary>
    public string Hostname { get; set; }
    /// <summary>
    /// 操作系统
    /// </summary>
    public string OperatingSystem { get; set; }
    /// <summary>
    /// 设备类型
    /// </summary>
    public string DeviceType { get; set; }
    /// <summary>
    /// 指纹格式
    /// </summary>
    public string CpeName { get; set; }

    public ProbeVersionInfo()
    {
        Production = string.Empty;
        Version = string.Empty;
        Information = string.Empty;
        Hostname = string.Empty;
        OperatingSystem = string.Empty;
        DeviceType = string.Empty;
        CpeName = string.Empty;
    }

    public void Parse(string input)
    {
        ProductionParse(input);
        VersionParse(input);
        InformationParse(input);
        HostnameParse(input);
        OperatingSystemParse(input);
        DeviceTypeParse(input);
        CpeNameParse(input);
    }
    private void ProductionParse(string input)
    {
        var pattern = "p/(?<val>.*)/";
        var reg = Regex.Match(input, pattern);
        if (!reg.Success)
        {
            return;
        }
        Production = reg.Result("${val}");
    }
    private void VersionParse(string input)
    {
        var pattern = "v/(?<val>.*)/";
        var reg = Regex.Match(input, pattern);
        if (!reg.Success)
        {
            return;
        }
        Version = reg.Result("${val}");
    }
    private void InformationParse(string input)
    {
        var pattern = "i/(?<val>.*)/";
        var reg = Regex.Match(input, pattern);
        if (!reg.Success)
        {
            return;
        }
        Information = reg.Result("${val}");
    }
    private void HostnameParse(string input)
    {
        var pattern = "h/(?<val>.*)/";
        var reg = Regex.Match(input, pattern);
        if (!reg.Success)
        {
            return;
        }
        Hostname = reg.Result("${val}");
    }
    private void OperatingSystemParse(string input)
    {
        var pattern = "o/(?<val>.*)/";
        var reg = Regex.Match(input, pattern);
        if (!reg.Success)
        {
            return;
        }
        OperatingSystem = reg.Result("${val}");
    }
    private void DeviceTypeParse(string input)
    {
        var pattern = "d/(?<val>.*)/";
        var reg = Regex.Match(input, pattern);
        if (!reg.Success)
        {
            return;
        }
        DeviceType = reg.Result("${val}");
    }
    private void CpeNameParse(string input)
    {
        var pattern = "cpe:/(?<val>.*)/";
        var reg = Regex.Match(input, pattern);
        if (!reg.Success)
        {
            return;
        }
        CpeName = reg.Result("${val}");
    }
}
